(define-module (parallelism)
  #:export (run-in-parallel))


(use-modules
 ;; for parallel forms
 (ice-9 threads)
 ;; for futures
 (ice-9 futures)
 ;; srfi 8 for receive form
 (srfi srfi-8))

#|
;; This has to be a macro, because ~parallel~ takes simple expressions instead
;; of lambdas to run. Such expressions would be evaluated as arguments, before
;; ~using-parallel-forms~ would be called with the results. ~parallel~ itself is
;; a macro, which is why it can take raw expressions instead of making the user
;; wrap expressions in lambdas.
(define-syntax run-in-parallel-forms
  (syntax-rules ()
    [(_ expr ...)
     (parallel expr ...)]))
|#


(define map-future
  (lambda (proc data)
    (map (lambda (data-part)
           (future (proc data-part)))
         data)
    ;; (let future-iter ([args data] [futures '()])
    ;;   (cond
    ;;    [(null? args) futures]
    ;;    [else
    ;;     (future-iter (cdr args)
    ;;                  (cons (future (proc (car args)))
    ;;                        futures))]))
    ))

(define map-touch
  (lambda (futures)
    (map (lambda (a-future)
           (touch a-future))
         futures)))

;; ~run-in-parallel~ is the piece of code, which you should adapt, if you want
;; to use something else than parallel forms.
(define-public run-in-parallel
  (lambda (proc data)
    "Apply proc to all elements in data in parallel."
    ;; Create futures and then touch the list of created futures. Return the
    ;; results of the futures.
    ;; (display (simple-format #f "Running ~a in parallel over ~a.\n" proc data))
    (display (simple-format #f "Running ~a in parallel.\n" proc))
    (map-touch
     (map-future proc data))))


;; Below example procedures for busy work are defined.


#;(define busy-work
  (lambda ()
    (let loop ([i 0])
      (cond
       [(< i 2e8) (loop (+ i 1))]
       [else i]))))

;; (run-in-parallel (lambda (_) (busy-work))
;;                  '(1 2 3 4))

#;(run-in-parallel
 (busy-work)
 (busy-work)
 (busy-work)
 (busy-work))
